package org.sith.taskrunner.gwt.server;

import java.lang.reflect.*;
import java.util.*;


public class ProxyConverter {


    public void convert(Object from, Object to, TargetChecker targetChecker) {
        Map alreadyConvertedObjects = new HashMap();
        convertObjects(from, to, alreadyConvertedObjects, targetChecker);

    }

    private void convertObjects(Object from, Object to, Map alreadyConvertedObjects, TargetChecker targetChecker) {


        alreadyConvertedObjects.put(from, to);
        Field[] fromFields = from.getClass().getDeclaredFields();
        Field[] toFields = to.getClass().getDeclaredFields();
        Map<String, Field> toFieldsMap = new HashMap<String, Field>();

        for (Field toField : toFields) {
            toFieldsMap.put(toField.getName(), toField);
        }

        Field field = null;

        for (Field declaredField : fromFields) {
            try {

                declaredField.setAccessible(true);

                field = toFieldsMap.get(declaredField.getName());
                field.setAccessible(true);

                Object value = (Object) declaredField.get(from);


                if (field == null || value == null) {
                    continue;
                }


                if (declaredField.getType().isEnum()) {
                    Enum anEnum = (Enum) value;
                    Class fieldClass = field.getType();
                    Method valueOfMethod = fieldClass.getDeclaredMethod("valueOf", String.class);
                    Object enumValue = valueOfMethod.invoke(anEnum, anEnum.name());
                    field.set(to, enumValue);
                } else if (value.getClass().isPrimitive()) {
                    field.set(to, value);
                } else if (value instanceof Collection && hasTargetGeneric(declaredField, targetChecker)) {
                    convertCollectionOfTargetObjects(to, alreadyConvertedObjects, field, (Collection) value, targetChecker);
                } else if (value instanceof Map) {
                    throw new IllegalStateException("Map object doesn't allowed here");
                } else if (alreadyConvertedObjects.containsKey(value)) {
                    field.set(to, alreadyConvertedObjects.get(value));
                } else if (targetChecker.checkClass(value.getClass()) && !alreadyConvertedObjects.containsKey(value)) {
                    convertChildTargetObject(to, alreadyConvertedObjects, field, value, targetChecker);
                } else {
                    field.set(to, value);
                }
            } catch (IllegalAccessException e) {
                throw new IllegalStateException(e);
            } catch (InstantiationException e) {
                throw new IllegalStateException(e);
            } catch (NoSuchMethodException e) {
                throw new IllegalStateException(e);
            } catch (InvocationTargetException e) {
                throw new IllegalStateException(e);
            } finally {
                if (field != null) {
                    field.setAccessible(false);
                }
                declaredField.setAccessible(false);
            }
        }
    }

    private void convertChildTargetObject(Object to, Map alreadyConvertedObjects, Field field, Object value, TargetChecker targetChecker) throws InstantiationException, IllegalAccessException {
        Object childObject =  to.getClass().newInstance();
        alreadyConvertedObjects.put(value, childObject);
        convertObjects(value, childObject, alreadyConvertedObjects, targetChecker);
        field.set(to, childObject);
    }

    private void convertCollectionOfTargetObjects(Object to, Map alreadyConvertedObjects, Field field, Collection value, TargetChecker targetChecker) throws InstantiationException, IllegalAccessException {
        Collection newColl = value.getClass().newInstance();
        for (Object fromCollectionItem : value) {
            Object toCollectionItem =  to.getClass().newInstance();
            if (alreadyConvertedObjects.containsKey(fromCollectionItem)) {
                newColl.add(alreadyConvertedObjects.get(fromCollectionItem));
            } else {
                newColl.add(toCollectionItem);
                convertObjects(fromCollectionItem, toCollectionItem, alreadyConvertedObjects, targetChecker);
            }
        }
        field.set(to, newColl);
    }


    private boolean hasTargetGeneric(Field field, TargetChecker targetChecker) {
        Type genericType = field.getGenericType();
        if (genericType instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) genericType;
            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
            StringBuilder builder = new StringBuilder();
            builder.append("<");
            for (Type generic : actualTypeArguments) {

                Class genericClazz = (Class) generic;
                if (targetChecker.checkClass(genericClazz)) {
                    return true;

                }
            }
        }
        return false;
    }


}
